/*******************************************
 * The SNIFFER Operations Library
 * -------------------------------
 *
 * C. Nunez 	(cnunez@stevens.edu)
 * W. Best		(wbest@stevens.edu)
 * K. Fuhrman 	(ktfuhrman@gmail.com)
 * M. DiAmore 	(mdiamore@gmail.com)
 *
 * Date created: 	23 November, 2009
 * Last edited:		14 May, 2010
 *
 * Summary
 * -------
 * This library contains basic routines for
 * rudimentary packet capture and sniffing.
 * It is based wholly on pcap. At the moment,
 * this library provides ways of extrapolating
 * the following:
 *
 *  - Link-layer frame information (Ethernet only; no ATM network to test this on.)
 *  - Transport datagram information (IP)
 *  - Session layer information.
 *
 * NOTE: This library is for console applications only! May or may not work with GUI...
 *
 *******************************************/
//=====================================================================
//	DEFINES
//=====================================================================
#define SNIFFER_CORE_OK		0
#define SNIFFER_CORE_FAIL	1

//=====================================================================
//	INCLUDE
//=====================================================================
#include "sniffer_core.hpp"
using namespace std;

//=====================================================================
//	PRIVATE GLOBALS
//=====================================================================

static pcap_t *sniffer_handler;				//This is defined in "sniffer_core.hpp"
boost::mutex core_io_mutex;				//Lock for console printing.

//Flags.
static bool print_eth_addr;					//Flag for ethernet printing.
static bool promisc_status;					//Flag for promiscuous mode setting.
static bool handler_started;				//Flag to denote handler status.
static bool	gui_running;					//Flag for GUI.

//Values.
static int SNIFFER_PKT_TIMEOUT;				//Maximum # of packets.

//=========================================
//	INTERNAL PROTOTYPES
//=========================================

int			cli_set_promisc(int mode);			//Sets device promiscuity before handler activation.
string		cli_select_device();				//select_device_gui() for CLI.		

int			gui_set_promisc();					//GUI method to set promiscuity. Returns error code.

void		set_handler_status(bool status);	//Sets handler status.

void		close_pcap();						//Closes a pcap handler.

//=====================================================================
//	CLI ENTRY POINT
//=====================================================================

int _sniffer_cli_main()
{
	//Print the welcome message.
	//--------------------------
	cout << "Welcome to the SNIFFER console." << endl;
	cout << "2009-2010, CPE-465 SNIFFER Team (Carlos Nunez, William Best, Kevin Fuhrman and Michael DiAmore)." << endl;
	cout << "This application is licensed under the BSD License, and can be copied and modified at will with proper acknowledgements." << endl;
	cout << "----------------------------------------------------------------------------------------------------------------------------" << endl;

	//Initialize.
	//-----------
	if (sniffer_core_init() != 0)
	{
		cout << "Initialization error." << endl;
		return SNIFFER_CORE_FAIL;
	}

#ifdef UNIX
	(void) signal(SIGINT,sig_handler);
#endif
	
	//=====================================================

	//Local variables.
	//-----------------
	
	string device_str;
	char * device;							//The device we're scanning with.
	char errbuf[PCAP_ERRBUF_SIZE];			//Error string.
	//but holding at 65536 for now.
	int choice;								//Choice integer for promiscuous mode. User-configurable.
	const int timeout = -1;				//Timeout for packet capturing in ms. User-configurable, but holding at 100 ms for now.
											//TODO: Replace with global timeout.
	
	/***************** END ****************/
	
	//TEST
	//----
	device_str = cli_select_device();
	if (device_str.size() == 0)	{
		dcout("main(): No device returned. Stop.");
		return 1;
	}

	device = (char *)device_str.c_str();
	
	//
	//	Determine promiscuous mode.
	//	---------------------------
	//	A brief note: promiscuous mode scanning is NOT recommended, as it most likely won't work as-is (most
	//	networks are switched) and is detectable. It also does not work on wireless adapters, as most of them have
	//	monitor mode disabled at the firmware level. A function to be created later will check the adapter
	//	and set this appropriately.
	//
	//	That is all. Carry on, brave soldier!
	//
	
	dcout("Enter '1' if you would like to enable promiscuous mode or '0' to keep it disabled: ");
	cin >> choice;
	printf("\n");			//Carriage return.
	if (choice < 0 || choice > 1)	{
		dcout("main(): Invalid choice entered! Defaulting to off.");
		choice = 0;
	}
	else
		choice = cli_set_promisc(choice);
	
	
	sniffer_handler = pcap_open_live(	device,			//Device to open scanning on.
									 65536,			//Scanning sample size.
									 choice,			//Promiscuous mode
									 timeout,		//Sample timeout.
									 errbuf);		//Error buffer.
	
	if (sniffer_handler == NULL)	{
		dcout("main(): Handler couldn't be created. Reason: " + ((string)errbuf));
		exit(0);
	}
	
	dcout("main(): Session handler created successfully.\n");
	
	//Set printing mode.
	//-----------------
	string	print_choice;
	cout	<< "Print ethernet or IP packets? (eth/[ip])" << endl;
	cin		>> print_choice;
	
	if (print_choice == "eth")
		print_eth_addr = true;
	else
		print_eth_addr = false;
	
	//Start printing!
	//---------------
	cout	<< "id"				<< setw(10) 
	<< "Source"			<< setw(strlen("Source") + 10) 
	<< "Destination"	<< setw(strlen("Destination") + 10) 
	<< "Type" << endl;
	
	set_handler_status(true);
	pcap_loop(sniffer_handler,2000,capture_packet,NULL);
	set_handler_status(false);

	//The end. TADA!
	dcout("main(): Application done. Quit.");
	
	return 0;
}





//-----------------------------------------------------------------------------------------------
//	sniffer_core_init():	Initializes the SNIFFER core. Returns 0 when successful, 1 on fail.
//-----------------------------------------------------------------------------------------------

int	sniffer_core_init()
{
	const char *fcn	= "sniffer_core_init";
	dcout(fcn,"Initializing SNIFFER core...");

	//Initialize flags.
	//-----------------
	dcout(fcn,"Initialing core flags");
	gui_running		= false;
	print_eth_addr	= false;
	promisc_status	= false;
	handler_started = false;

	//Initialize database.
	//--------------------
	dcout(fcn,"Initializing core database");
	if ( CreateDb("SNIFFER_DB") == SQLITE_OK )
		dcout(fcn,"Database created.");
	else
	{
		dcout(fcn,"Database creation failed.");
		return 1;
	}

	//Done.
	//-----
	dcout(fcn,"Done.");
	return 0;
}


//=========================================================
//	HANDLER CONTROL
//=========================================================

//-----------------------------------------------------------------------------
//	bool get_handler_status:		Obvious.
//-----------------------------------------------------------------------------
bool get_handler_status()
{
	return handler_started;
}

//-----------------------------------------------------------------------------
//	int set_handler_status(bool status):	Sets the status of the handler...
//-----------------------------------------------------------------------------
void set_handler_status(bool status)
{
	const char *fcn = "set_handler_status";
	dcout_inline(fcn,"Setting handler status.");
	handler_started = status;
	
	//DEBUG.
	//------
	if (handler_started)
		dcout_inline("...now TRUE.");
	else
		dcout_inline("...now FALSE.");

}

//-----------------------------------------------------------------------------
//	void gui_start_capture(string device):	Initializes a capture session.
//-----------------------------------------------------------------------------

int gui_start_capture(string device)
{
	char errbuf[PCAP_ERRBUF_SIZE];											//error buffer for pcap_open_live
	const char *fcn = "gui_start_capture";
	dcout(fcn,"Starting handler.");
	if (!get_handler_status() && device != "")				//Make sure no other handler's in session.
	{
		//Create a handler.
		//----------------
		sniffer_handler = pcap_open_live(	 device.c_str(),			//Device to open scanning on.
											 65536,			//Scanning sample size.
											 0,				//Promiscuous mode, off by default
											 SNIFFER_PKT_TIMEOUT,		//Sample timeout. If -1, it goes FOREVER....
											 errbuf);		//Error buffer.

		//Exit if it didn't make one
		//---------------------------
		if (sniffer_handler == NULL)
		{
			dcout(fcn, "Could not make a handler! Reason: " + (string)errbuf);
			
			return 1;
		}

		set_handler_status(true);			//Set handler status.
		pcap_loop(sniffer_handler,SNIFFER_PKT_TIMEOUT,capture_packet,NULL);		//Go go gogogogogogo
		set_handler_status(false);			//After pcap_loop is broken, reset handler status
		dcout(fcn,"Done.");
		return 0;
	}

	else
	{
		dcout(fcn,"Could not start handler. Reason should have been provided.");
		return 1;
	}
}

//-----------------------------------------------------------------------------
//	void gui_stop_capture():	Breaks the pcap_loop to stop session.
//-----------------------------------------------------------------------------
int gui_stop_capture()
{
	const char *fcn = "gui_stop_capture";
	dcout(fcn,"Stopping.");
	if (get_handler_status())
		pcap_breakloop(sniffer_handler);					//Stops whatever active loop is in session.
	else	
	{
		dcout(fcn,"Handler could not be stopped. Reason should have been explained.");
		return 1;
	}

	set_handler_status(false);
	dcout(fcn,"Done.");
	return 0;
}

//-----------------------------------------------------------------------------
//	void gui_end_capture(string device):		Completely terminates.
//-----------------------------------------------------------------------------
int gui_end_capture()
{
	const char *fcn = "gui_end_capture";
	if (gui_stop_capture() == 0)			//Go ahead and end if stop is successful.
		close_pcap();
	else
	{
		dcout(fcn,"Handler could not be terminated. Reason should have been given.");
		return 1;
	}

	dcout(fcn,"Done.");
	return 0;
}

//=========================================================
//	DEVICE SELECTION CONTROL
//=========================================================
//---------------------------------------------------------------------
// vector<string> select_device_gui()
// -----------------------------------
//
// This function returns an array of devices selectable for packet
// capture. Pcap selects their devices by whatever name is in their driver.
//
// Returns NULL if no devices were found.
//---------------------------------------------------------------------

vector<string> select_device_gui()
{
	const char	*fcn	=	"select_device_gui()";
	dcout(fcn,"Entering device selection method.");

	//Define some variables.
	//----------------------
	char		*buf = NULL;				//Buffer for errors reported by PCAP.
	pcap_if_t	*all_devices;		//Enumeration of all devices sent back by PCAP.
	vector<string> returned_device_list;

	//Find some devices. WinPcap uses findalldevs_ex, libpcap uses findalldevs.
	//------------------------------------------------------------------------
	dcout(fcn,"Finding available devices.");
	if (	(pcap_findalldevs(&all_devices, buf) == -1) 	)
	{
		dcout(fcn,"No devices returned! Error description" + (string)buf);
		return returned_device_list;		//Return empty vector.
	}

	//Aggregate devices and populate device list.
	//-------------------------------------------
	pcap_if_t		*single_device;				//Single device item.

	for (single_device = all_devices; single_device; single_device = single_device->next)
	{
		//Winpcap uses pcap_if_t::description to store device description, whereas
		//libpcap uses pcap_if_t::name.
		//-------------------------------------------------------------------------
		#if	defined(_WIN32) || defined(WIN32)
				string device_description = (string)single_device->description;
		#else
				string device_description = (string)single_device->name;
		#endif
		
		//Push it to the vector now.
		//--------------------------
		dcout(fcn,"Found: " + device_description);
		returned_device_list.push_back(device_description);
		dcout(fcn,"Device pushed.");
	}

	//Verify device list populated successfully.
	//------------------------------------------
	dcout(fcn,"The device list is: ");
	for (vector<string>::iterator it = returned_device_list.begin(); it != returned_device_list.end(); ++it)
		dcout("		" + *it);
	
	//Completed.
	//----------
	dcout(fcn,"Done.");
	return returned_device_list;
}

//-------------------------------------------------------------------
//	string get_device_from_name(string device_descr):  Gets dev ID.
//-------------------------------------------------------------------

string get_device_from_name(string device_descr)
{
	const char	*fcn		=	"get_device_from_name";
	pcap_if_t	*all_devices;
	char		buf[PCAP_ERRBUF_SIZE];

	//Get all devices.
	//----------------
	if ( pcap_findalldevs(&all_devices,buf)	== -1)
	{
		dcout(fcn,"Error in finding devices! Reason: " + (string)buf);
		return "";
	}

	//Now match.
	//----------
	dcout(fcn,"Getting device ID...");
	pcap_if_t	*single_device;
	for (single_device = all_devices; single_device; single_device = single_device->next)
	{
		if ( (string)single_device->description == device_descr )		//Found it; done.
			return single_device->name;
	}

	//If we're here, nothing was found.
	//--------------------------------
	dcout(fcn,"No devices found. :-(");
	return "";
}

/*	string select_device(char buf)
 * 	----------------------------------
 *
 * 	Description: Returns the list of all capturing devices available
 *  on the system. Windows uses pcap_findalldevs_ex, Linux/UNIX uses
 *  pcap_findalldevs instead.
 *
 *	NOTE: This function is for console mode ONLY!
 *
 *  Returns: 	string device
 *  Requires:	char buf
 *
 */

string	cli_select_device()
{
	const char	*fcn	=	"cli_select_device()";
	
	//Define some variables.
	//----------------------
	char		*buf = NULL;				//Buffer for errors reported by PCAP.
	pcap_if_t	*all_devices;		//Enumeration of all devices sent back by PCAP.
	vector<string> returned_device_list;
#if defined(WIN32) | defined(_WIN32)
	vector<string> returned_device_descriptions;
#endif
	
	//Find some devices. WinPcap uses findalldevs_ex, libpcap uses findalldevs.
	//------------------------------------------------------------------------
	dcout("Finding available devices.");
	if (	(pcap_findalldevs(&all_devices, buf) == -1) 	)
	{
		dcout(fcn,"Error in findidng devices! Description: " + (string)buf);
		return NULL;		//Return null pointer.
	}
	
	//Aggregate devices and populate device list.
	//-------------------------------------------
	pcap_if_t		*single_device;				//Single device item.
	
	for (single_device = all_devices; single_device; single_device = single_device->next)
	{
		//Winpcap uses pcap_if_t::description to store device description, whereas
		//libpcap uses pcap_if_t::name.
		//-------------------------------------------------------------------------
	
		//Push it to the vector now.
		//--------------------------
		returned_device_list.push_back((string)single_device->name);

#if defined(WIN32) | defined(_WIN32)
		returned_device_descriptions.push_back((string)single_device->description);
#endif
		
	}

	//Error out if no devices were returned.
	//--------------------------------------
	if (returned_device_list.size() <= 0)
	{
		dcout(fcn,"No devices returned. Are you sure this is running as root?");
		return NULL;
	}
	
	//Present device list to user for selection.
	//------------------------------------------
#if defined(WIN32) | defined(_WIN32)
	dcout("Select a networking device below (1 - " + itoch(returned_device_descriptions.size()) + ")");
	
	for (unsigned int i = 0; i < returned_device_descriptions.size(); i++)
		dcout(itoch(i+1) + ": " + returned_device_descriptions[i]);
#else
	dcout("Select a networking device below (1 - " + itoch(returned_device_list.size()) + ")");
	
	for (unsigned int i = 0; i < returned_device_list.size(); i++)
		dcout(itoch(i+1) + ": " + returned_device_list[i]);
#endif

	//Select interface.
	dcout_inline("Enter the device number here: ");
	int choice;
	cin >> choice;

	if (choice < 1 || choice > (int) returned_device_list.size())		//If choice is out of bounds, free and send ERROR.
	{
		dcout("Your choice is out of range. Exiting...");
		return "";
	}
	else
		return returned_device_list[choice-1];	//pcap_lookupnet(...) and pcap_open_live(...) want non-const char

	dcout(fcn,"Function went out of scope...");
	return "";		//Shouldn't reach this point, but return NULL if it does.
}



//=========================================================
//	PROMISCUOUS METHOD CONTROL
//=========================================================
int		cli_set_promisc(int mode)
{
	switch (mode)
	{
		case 0:	dcout("cli_set_promisc(): Promiscuous mode disabled.");
				return 0;

		case 1:		dcout("cli_set_promisc(): Promiscuous mode enabled.");
					promisc_status = true;
					return 1;

		default:	dcout("cli_set_promisc(): Invalid choice " + itoch(mode) + " detected. Defaulting to disabled...");
					return 0;
	}

	dcout("cli_set_promisc(): Shouldn't be here...Defaulting to disabled.");
	return 0;
}

//----------------------------------------------------------------------
//	int gui_set_promisc():	Sets promiscuous mode with GUI mode.
//							Handler needs to be stopped temporarily.
//----------------------------------------------------------------------

int gui_set_promisc()
{
	const char *fcn = "gui_set_promisc()";
	dcout(fcn,"Setting promiscuous mode for handler.");

	if (get_handler_status())			//Do it if promiscuous mode is on.
	{
		dcout(fcn,"Stopping handler.");
		pcap_breakloop(sniffer_handler);		//Stops the pcap_loop.
		if (!check_promisc_status())
			pcap_set_promisc(sniffer_handler,1);	//Set it. 
		else
			pcap_set_promisc(sniffer_handler,0);	//Unset it.
		dcout(fcn,"Starting handler.");
		pcap_loop(sniffer_handler,SNIFFER_PKT_TIMEOUT,capture_packet,NULL);	//Reusme
		return 0;
	}
	else
	{
		dcout(fcn,"Promiscuous mode could not be set.");
		return 1;
	}
}


//---------------------------------------------------------------
//	void gui_set_running():	Flag to let main know that GUI is on.
//---------------------------------------------------------------
void gui_set_running()
{
	const char *fcn = "gui_set_running";
	if (!gui_running)
		gui_running = true;
	dcout(fcn,"GUI is on");
}

//---------------------------------------------------------------
//	bool is_gui_running(): Well...
//---------------------------------------------------------------
bool is_gui_running()
{
	return gui_running;
}

//--------------------------------------------------------------------------------------------------
//	EXTERN bool check_promisc_status():	Checks whether device is in promiscuous mode or not.
//--------------------------------------------------------------------------------------------------
bool check_promisc_status()
{
	const char *fcn = "check_promisc_status";
	if (get_handler_status())
		return promisc_status;
	else
	{
		dcout(fcn,"Handler has not been started.");
		return false;
	}
}


//=====================================================================
//	EXTERNAL SETTERS AND GETTERS.
//=====================================================================

//--------------------------------------------------------------------------------------------------
//	EXTERN bool etheraddr_print_enabled(): Tells caller whether ethernet address printing is on.
//--------------------------------------------------------------------------------------------------
bool etheraddr_print_enabled()
{
	return print_eth_addr;
}

//----------------------------------------------------------------------------------------------
//	EXTERN int set_pkt_limit(unsigned int val): Sets the maximum number of packets to receive.
//												Defaults to 2000.
//----------------------------------------------------------------------------------------------
int set_pkt_limit(unsigned int val)
{
	const char *fcn = "set_pkt_limit";
	if (val <= 0)
	{
		dcout(fcn,"Invalid value detected!");
		return 1;
	}
	else
	{
		dcout(fcn,"Setting timeout");
		SNIFFER_PKT_TIMEOUT = val;
		return 0;
	}
}

//-----------------------------------------------------------------------
//	EXTERN int get_pkt_limit():	Gets the packet capture ceiling
//-----------------------------------------------------------------------
int get_pkt_limit(unsigned int val)
{
	const char *fcn = "get_pkt_limit";
	if (SNIFFER_PKT_TIMEOUT != 0)
		return SNIFFER_PKT_TIMEOUT;
	else
	{
		dcout(fcn,"Timeout not set!");
		return 0;
	}
}


//=====================================================================
//	SUPPORT FUNCTIONS.
//=====================================================================

/***********************************************
void save_db(vector<vector<string>> packetData
-----------------------------------------------
Saves everything to DB.
************************************************/

int save_db(const char *name, vector< vector<string> > packetList)
{
	const char *fcn		 = "save_db";
	const char *dbname	 = name;		//Name the database.

	//Create the DB.
	if (CreateDb(name) != SQLITE_OK)
	{
		cout << "Not able to save!" << endl;
		return SNIFFER_CORE_FAIL;
	}

	//Open
	//----
	if (OpenConnection(name) != SQLITE_OK)
	{
		cout << "Not able to open: " << name << "!" << endl;
		return SNIFFER_CORE_FAIL;
	}

	//Sift through packet list and save each packet.
	//----------------------------------------------
	for (unsigned int i = 0; i < packetList.size(); i++)
	{
		dcout(fcn,"Inserting packet " + itoch(i));
		Insert(packetList[i][4].c_str(), packetList[i][5].c_str(), packetList[i][6].c_str(), packetList[i][7].c_str());
	}

	dcout(fcn,"Done!");
	CloseConnection();		//Close dbconn
	
	return SNIFFER_CORE_OK;
}


/******************************************************************************************
 
 void print_packet
 -----------------
	Prints the packet out to console.
 
 ******************************************************************************************/

void print_packet(const char *mode_t)
{
	boost::mutex::scoped_lock pp_lock(core_io_mutex);			//Restrict.

	string mode		=	(string)mode_t;							//Quick fix for lack of "==" support for const char dtypes with gcc.

	//Get packet information.
	//-----------------------
	std::vector<std::string> pkt_info	=	sniffer_pkt::Instance()->get_sniffer_pkt();
	
	std::string id	=	pkt_info[0];
	std::string type = 	pkt_info[1];
	string src, dst;
	
	if (mode == "eth")
	{
		src	=	pkt_info[2];
		dst	=	pkt_info[3];
	}
	else if (mode == "ip")
	{
		src	=	pkt_info[4];
		dst	=	pkt_info[5];
	}
	else {
		cout << "Invalid mode type specified!" << endl;
		return;
	}
	
	cout << id;		cout.width(src.length()  + 5);
	cout << src;	cout.width(dst.length()  + 5);
	cout << dst;	cout.width(type.length() + 5);
	cout << type					<< endl;

	//scoped_lock destructor releases the lock.
}

//===================================		  END		===================================

//---------------------------------------------------------------------
//	Alternate constructor defs.
//---------------------------------------------------------------------
void	dcout(std::string msg)
{	dcout("",msg.c_str());		}

void	dcout(std::string fcn, std::string msg)
{	dcout(fcn.c_str(), msg.c_str());	}

void	dcout_inline(std::string msg)
{	dcout_inline("",msg.c_str());	}

void	dcout_inline(std::string fcn, std::string msg)
{	dcout_inline(fcn.c_str(), msg.c_str());	}

void	dcout(const char *msg)
{	dcout("",msg);	}

void 	dcout_inline(const char *msg)
{	dcout_inline("",msg);	}


//---------------------------------------------------------------------
// void dcout(const char *fcn, const char *msg)
// --------------------------------------------
//
// Prints debug messages to a console.
//
//---------------------------------------------------------------------

void dcout(const char *fcn, const char *msg)
{
	boost::mutex::scoped_lock dcout_lock(core_io_mutex);

	if (strcmp(fcn,"") != 0)
		cout << fcn << "(): " << msg << endl;
	else
		cout << msg << endl;
}

//---------------------------------------------------------------------
// void dcout_line(const char *fcn, const char *msg)
// --------------------------------------------
//
// Prints debug messages to a console (inline).
//
//---------------------------------------------------------------------

void dcout_inline(const char *fcn, const char *msg)
{
	boost::mutex::scoped_lock dcout_lock(core_io_mutex);

	if (strcmp(fcn,"") != 0)
		cout << fcn << "(): " << msg << " ";
	else
		cout << msg << " ";
}

//----------------------------------------------------------------------------
//	u_short r_ntohs(u_short t):		Returns proper byte order based on OS
//									Should REALLY be based on CPU type (endianness)
//									but this was written on Intel arch and
//									was needed for quick Windows port.
//----------------------------------------------------------------------------
u_short r_ntohs(u_short t)
{
#if defined(WIN32) || defined(_WIN32)
	return ntohs(t);
#else
	return ntohs(t);
#endif
}


/**************************************
 
 void sig_handler
 ----------------
	Catches external signals. UNIX only.
 
 **************************************/
#ifdef UNIX
void sig_handler(int signal)
{
	psignal(signal, "\nSignal caught: ");
	
	//Close pcap.
	//----------
	if (signal == SIGINT)
	{
		if (sniffer_handler != NULL)
			close_pcap();
		else
		{
			dcout("SNIFFER closing.");
			exit(0);
		}
	}
}
#endif
/**************************************

close_pcap()
------------
	Closes pcap session.

***************************************/

void close_pcap()
{
	const char *fcn = "close_pcap()";
	
	if (sniffer_handler != NULL)
	{
		dcout(fcn,"SNIFFER closing...");
		pcap_close(sniffer_handler);
	}
}
